type Timeout = NodeJS.Timeout | number
type DataType = "boolean" | "string" | "number" | "array" | "function" | "date" | "file" | "blob" | "object" | "undefined" | "null" | false

/* 类型判断 */
export function dataType(v: any): DataType {
  if (typeof v === "boolean") return "boolean"
  else if (typeof v === "string") return "string"
  else if (typeof v === "number") return "number"
  else if (typeof v === "undefined") return "undefined"
  else if (v instanceof File) return "file"
  else if (v instanceof Blob) return "blob"
  else if (v instanceof Array) return "array"
  else if (v instanceof Function) return "function"
  else if (v instanceof Date) return "date"
  else if (v instanceof Object) return "object"
  else if (v === null) return "null"
  else return false
}

/* 是否为 String */
export function isString(v: any): v is string {
  return dataType(v) === "string"
}

/* 是否为 Number */
export function isNumber(v: any): v is number {
  return dataType(v) === "number"
}

/* 是否为 Array */
export function isArray(v: any): v is Array<any> {
  return dataType(v) === "array"
}

/* 是否为 Function */
export function isFunction(v: any): v is Function {
  return dataType(v) === "function"
}

/* 是否为 Date */
export function isDate(v: any): v is Date {
  return dataType(v) === "date"
}

/* 是否为 Boolean */
export function isBoolean(v: any): v is boolean {
  return dataType(v) === "boolean"
}

/* 是否为 Object */
export function isObject(v: any): v is object {
  return dataType(v) === "object"
}

/* 是否为 File */
export function isFile(v: any): v is File {
  return dataType(v) === "file"
}

/* 是否为 Blob */
export function isBlob(v: any): v is Blob {
  return dataType(v) === "blob"
}

/* 是否为 undefined */
export function isUndefined(v: any): v is undefined {
  return dataType(v) === "undefined"
}

/* 是否为 null */
export function isNull(v: any): v is null {
  return dataType(v) === "null"
}

/* 防抖: 将 fn 封装成防抖函数后返回 */
export function useDebounce(fn: Function, delay: number = 0): Function {
  let timer: Timeout
  return (...params: any[]) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn(...params)
    }, delay)
  }
}

/* 节流: 将 fn 封装为节流函数后返回 */
export function useThrottle(fn: Function, delay?: number): Function {
  /* 使用计时器 */
  let timer: Timeout // 计时器
  async function delayFn(...params: any[]) {
    if (!timer) {
      await fn(...params)
      timer = setTimeout(() => {
        clearTimeout(timer)
        timer = 0
      }, delay)
    }
  }

  /* 使用异步函数 */
  let loading = false // 是否正在等待
  async function loadFn(...params: any[]) {
    if (!loading) {
      loading = true
      await fn(...params)
      loading = false
    }
  }

  if (isNumber(delay)) return delayFn
  else return loadFn
}

/* 深度克隆对象 */
export function deepClone<TData>(data: TData): TData {
  let value: any

  // 数组克隆
  if (isArray(data)) {
    value = []
    data.forEach((v) => {
      value.push(deepClone(v))
    })
  }
  // 对象克隆
  else if (isObject(data)) {
    value = {}
    for (let key in data) {
      value[key] = deepClone(data[key])
    }
  }
  // 基础类型直接返回
  else value = data

  return value
}

/* 数组去重 */
export function uniqueArray<TNode>(arr: TNode[]): TNode[] {
  if (isArray(arr)) {
    const set = new Set(arr)
    return Array.from(set)
  } else return arr
}

type TransformResult = { value: object[]; pointers: object } | boolean
interface TransformConfig {
  /* 节点唯一标识 key */
  key: string
  /* 指向父节点标识 */
  parent: string
  /* 指向子节点标识 */
  children: string
  /* 对节点进行单独处理 */
  handler: <TNode>(item: TNode) => void
}
/* 树转列表，并为子节点插入父指针 */
export function treeToList(tree: any[] | object, config?: TransformConfig): TransformResult {
  const baseConfig: TransformConfig = {
    key: "id", // 唯一标识字段
    parent: "parent", // 指向父节点的指针
    children: "children", // 子节点字段
    handler: (item) => {}, // 二次加工数据项的方法
    ...(isObject(config) ? config : {})
  }

  /* 对象参数转为数组 */
  if (isObject(tree)) tree = [tree]

  if (isArray(tree)) {
    const list: any[] = []
    const pointers = {}

    const { key, parent: parentKey, children: childrenKey, handler } = baseConfig

    let items = [...tree]

    while (items.length) {
      // 取出当前项
      const current = items.shift()
      // 获取 id, children
      const { [childrenKey]: children = [], [key]: id } = current
      // 为 children 插入父指针
      const childrenItems = ((children as any[]) || []).map((child: object) => {
        child[parentKey] = id
        return child
      })

      items = [...items, ...childrenItems]

      handler(current)

      list.push(current)

      pointers[id] = current
    }

    return { value: list, pointers }
  } else {
    return false
  }
}

/* 列表转树 */
export function listToTree(list: any[], config?: TransformConfig): TransformResult {
  const baseConfig: TransformConfig = {
    key: "id", // 唯一标识字段
    parent: "parent", // 指向父级的字段
    children: "children", // 子节点字段
    handler: (item) => {}, // 二次加工数据项的方法
    ...(isObject(config) ? config : {})
  }

  if (isArray(list)) {
    const tree: any[] = []
    const pointers = {}
    const { key, parent: parentKey, children: childrenKey, handler } = baseConfig

    // 循环节点，设置指针
    list.forEach((item) => {
      const { [key]: id } = item
      pointers[id] = item
    })

    // 再次循环节点，插入子节点
    list.forEach((item) => {
      const { [parentKey]: parent } = item
      handler(item)
      if (pointers[parent]) {
        if (pointers[parent][childrenKey]) pointers[parent][childrenKey].push(item)
        else pointers[parent][childrenKey] = [item]
      } else {
        tree.push(item)
      }
    })

    return { value: tree, pointers }
  } else {
    return false
  }
}

/* 复制文本到剪切版 */
export function copyText(text: string): Promise<void> {
  try {
    /* 使用浏览器自带剪切版功能 */
    return window.navigator.clipboard.writeText(text)
  } catch {
    /* 使用 document.execCommand 剪切 */
    return new Promise((resolve, reject) => {
      try {
        // 创建一个 textarea 储存文本
        const element = document.createElement("textarea")
        element.value = text
        // 将元素插入到界面外
        element.setAttribute("readonly", "")
        element.style.contain = "strict" // 告知浏览器不渲染该屏幕外元素
        element.style.position = "absolute"
        element.style.left = "-9999px"
        document.body.appendChild(element)
        // 选中
        element.select()
        // 复制剪切板
        document.execCommand("copy")

        // 清除元素
        document.body.removeChild(element)

        // 存在选中区域时，清除浏览器的选中
        const selection = document.getSelection()
        const originalRange = selection ? selection.rangeCount > 0 && selection.getRangeAt(0) : null
        if (originalRange) {
          selection?.removeAllRanges()
          selection?.addRange(originalRange)
        }

        // 清除激活元素
        const previouslyFocusedElement = document.activeElement as HTMLElement
        if (previouslyFocusedElement) previouslyFocusedElement.focus()

        resolve()
      } catch {
        reject()
      }
    })
  }
}
